//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.client.ui;

import tv.dyndns.kishibe.client.Sanitize;

import com.google.gwt.user.client.ui.HTML;

public class Canvas extends HTML {
	private static final String BUFFER_PREFIX = "<p style='position:relative;overflow:visible;z-index:5;'>";
	private static final String BUFFER_SURFIX = "</p>";
	private StringBuffer buffer;
	private static final int INITIAL_BUFFER_SIZE = 1024;
	private static final char[] LETTER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
	private static final double EPS = 0.00001;
	private int offsetX = 0;
	private int offsetY = 0;

	public Canvas() {
		super("<p style='position:relative;'></p>");
	}

	public void prepare() {
		int initialBufferSize = INITIAL_BUFFER_SIZE;
		if (buffer != null) {
			initialBufferSize = Math.max(initialBufferSize, buffer.length() * 2);
		}

		buffer = new StringBuffer(initialBufferSize);
		buffer.append(BUFFER_PREFIX);
	}

	public void present() {
		buffer.append(BUFFER_SURFIX);
		setHTML(buffer.toString());
	}

	public void drawText(int x, int y, int fontSize, String color, String s) {
		buffer.append("<span style='position:absolute;left:");
		buffer.append(x);
		buffer.append("px;top:");
		buffer.append(y);
		buffer.append("px;font-size:");
		buffer.append(fontSize);
		buffer.append("px;color:#");
		buffer.append(color);
		buffer.append(";'>");
		buffer.append(Sanitize.getInstance().html(s));
		buffer.append("</span>");
	}

	private void addVerticalLine(int x, int y, int height, String color) {
		buffer.append("<span style='position:absolute;left:");
		buffer.append(x);
		buffer.append("px;top:");
		buffer.append(y);
		buffer.append("px;height:");
		buffer.append(height);
		buffer.append("px;width:1px;clip:rect(0,1px,");
		buffer.append(height);
		buffer.append("px,0);background-color:#");
		buffer.append(color);
		buffer.append(";'></span>");
	}

	public static String makeColor(double r, double g, double b) {
		final StringBuffer buffer = new StringBuffer();
		final int rr = (int) (r * 255.0);
		final int gg = (int) (g * 255.0);
		final int bb = (int) (b * 255.0);
		buffer.append(LETTER[rr / 16]);
		buffer.append(LETTER[rr % 16]);
		buffer.append(LETTER[gg / 16]);
		buffer.append(LETTER[gg % 16]);
		buffer.append(LETTER[bb / 16]);
		buffer.append(LETTER[bb % 16]);
		return buffer.toString();
	}

	public void drawLine(double x1, double y1, double x2, double y2, double width, String color) {
		x1 += offsetX;
		y1 += offsetY;
		x2 += offsetX;
		y2 += offsetY;

		if (Math.abs(x1 - x2) < EPS) {
			drawLineVertical(x1, y1, y2, width, color);
		} else {
			drawLineNormal(x1, y1, x2, y2, width, color);
		}
	}

	private void drawLineNormal(double x1, double y1, double x2, double y2, double width, String color) {
		final double r = width * 0.5;
		if (x1 > x2) {
			final double x = x1;
			x1 = x2;
			x2 = x;
			final double y = y1;
			y1 = y2;
			y2 = y;
		}

		final int beginX = (int) (x1 - r - EPS);
		final int endX = (int) (x2 + r + EPS) + 1;

		// 直線の式計算
		final double a = (y2 - y1) / (x2 - x1);
		final double theta = Math.atan(a);
		final double upperX1 = x1 + r * Math.cos(theta - Math.PI * 0.5);
		final double upperY1 = y1 + r * Math.sin(theta - Math.PI * 0.5);
		final double upperX2 = x2 + r * Math.cos(theta - Math.PI * 0.5);
		final double upperB = upperY1 - a * upperX1;

		final double lowerX1 = x1 + r * Math.cos(theta + Math.PI * 0.5);
		final double lowerY1 = y1 + r * Math.sin(theta + Math.PI * 0.5);
		final double lowerX2 = x2 + r * Math.cos(theta + Math.PI * 0.5);
		final double lowerB = lowerY1 - a * lowerX1;

		// 描画
		// 通常とは上下が逆になることに注意
		for (int xx = beginX; xx < endX; ++xx) {
			// 上境界計算
			double upper = Double.MAX_VALUE;
			double lower = Double.MIN_VALUE;
			{
				// 円1
				double h = r * r - (x1 - xx) * (x1 - xx);
				if (h >= 0.0) {
					h = Math.sqrt(h);
					upper = Math.min(upper, y1 - h);
					lower = Math.max(lower, y1 + h);
				}
			}
			{
				// 円2
				double h = r * r - (x2 - xx) * (x2 - xx);
				if (h >= 0.0) {
					h = Math.sqrt(h);
					upper = Math.min(upper, y2 - h);
					lower = Math.max(lower, y2 + h);
				}
			}

			if (upperX1 < xx && xx < upperX2) {
				final double y = a * xx + upperB;
				upper = Math.min(upper, y);
			}

			if (lowerX1 < xx && xx < lowerX2) {
				final double y = a * xx + lowerB;
				lower = Math.max(lower, y);
			}

			if (upper != Double.MAX_VALUE && lower != Double.MIN_VALUE) {
				final int yy = (int) upper;
				final int height = (int) (lower - upper) + 1;
				addVerticalLine(xx, yy, height, color);
			}
		}
	}

	private void drawLineVertical(double x, double y1, double y2, double width, String color) {
		final double r = width * 0.5;

		// y1を上に持ってくる
		if (y1 > y2) {
			final double y = y1;
			y1 = y2;
			y2 = y;
		}

		final int beginX = (int) (x - r - EPS);
		final int endX = (int) (x + r + EPS) + 1;
		for (int xx = beginX; xx < endX; ++xx) {
			double h = r * r - (x - xx) * (x - xx);
			if (h < 0.0) {
				continue;
			}

			h = Math.sqrt(h);

			addVerticalLine(xx, (int) (y1 - h), (int) (y2 - y1 + h * 2.0), color);
		}
	}

	public void setOffset(int x, int y) {
		offsetX = x;
		offsetY = y;
	}
}
